#include <cmath>
#include <QImage>
#include "model.h"
#include "mathhelper.h"
#include "MemoryHelper.h"
#include "FilterProcessor.h"
#include "FilterMaskFactory.h"

Model::Model()
{
    image = new PixelBuffer;
	tmpBuffer = new PixelBuffer;
	histogram = new ImageHistogram;
}

Model::~Model()
{
    delete image;
	delete histogram;
	delete tmpBuffer;
}

PixelBuffer* Model::Image()
{
    return image;
}

ImageHistogram* Model::Histogram()
{
	return histogram;
}

void Model::CopyTmpBuffer()
{
	for(int y = 0; y < image->Height(); y++)
    {
        for(int x = 0; x < image->Width(); x++)
        {
			tmpBuffer->Pixels()[y][x].R = image->Pixels()[y][x].R;
			tmpBuffer->Pixels()[y][x].G = image->Pixels()[y][x].G;
			tmpBuffer->Pixels()[y][x].B = image->Pixels()[y][x].B;
        }
    }

	NotifyImageUpdated(true);
}

bool Model::LoadImage(QString& path)
{
    QImage* srcImage = new QImage(path);

    if(image == NULL)
        return false;

    int width = srcImage->width(), height = srcImage->height();

    image->Resize(srcImage->width(), srcImage->height(), false);
	tmpBuffer->Resize(srcImage->width(), srcImage->height(), false);
    for(int y = 0; y < height; y++)
    {
        for(int x = 0; x < width; x++)
        {
            image->Pixels()[y][x].R = qRed(srcImage->pixel(x, y));
            image->Pixels()[y][x].G = qGreen(srcImage->pixel(x, y));
            image->Pixels()[y][x].B = qBlue(srcImage->pixel(x, y));
			tmpBuffer->Pixels()[y][x].R = image->Pixels()[y][x].R;
			tmpBuffer->Pixels()[y][x].G = image->Pixels()[y][x].G;
			tmpBuffer->Pixels()[y][x].B = image->Pixels()[y][x].B;

        }
    }

    NotifyImageUpdated(true);

    delete srcImage;

    return true;
}

void Model::ApplyFilter(FilterType filterType, void* filterParameter)
{
    g_FilterProcessor.ApplyFilter(image, filterType, filterParameter);
	CopyTmpBuffer();
    NotifyImageUpdated(true);
}

void Model::Inverse()
{
    Pixel** rowPtr;
    Pixel* columnPtr;
    int width = image->Width(), height = image->Height();

    rowPtr = image->Pixels();
    for(int y = 0; y < height; y++)
    {
        columnPtr = *rowPtr;
        for(int x = 0; x < width; x++)
        {
            columnPtr->R = 255 - columnPtr->R;
            columnPtr->G = 255 - columnPtr->G;
            columnPtr->B = 255 - columnPtr->B;

            columnPtr++;
        }
        rowPtr++;
    }
	CopyTmpBuffer();
    NotifyImageUpdated(true);
}


void Model::Grayscale()
{
    Pixel** rowPtr;
    Pixel* columnPtr;
    uchar grayScaleColor;
    int width = image->Width(), height = image->Height();

    rowPtr = image->Pixels();
    for(int y = 0; y < height; y++)
    {
        columnPtr = *rowPtr;
        for(int x = 0; x < width; x++)
        {
            grayScaleColor = (columnPtr->R + columnPtr->G + columnPtr->B) / 3;
            columnPtr->R = grayScaleColor;
            columnPtr->G = grayScaleColor;
            columnPtr->B = grayScaleColor;

            columnPtr++;
        }
        rowPtr++;
    }
	CopyTmpBuffer();
    NotifyImageUpdated(true);
}

void Model::ResizeDirectMapping(int factor, ResizeDirectMappingMode mode)
{
    switch(mode)
    {
    case Repeat:
        ResizeDirectMapping_Repeat(factor);
        break;
    case Interpolate:
        ResizeDirectMapping_Interp(factor);
        break;
    }

}
void Model::ResizeReverseMapping(int width, int height, ResizeReverseMappingMode mode)
{
    switch(mode)
    {
    case Order0:
        ResizeReverseMapping_Order0(width, height);
        break;
    case Bilinear:
        ResizeReverseMapping_Bilinear(width, height);
        break;
    }
}

void Model::ResizeDirectMapping_Repeat(int factor)
{

    Pixel** oldPixels = image->Pixels();
    int oldWidth = image->Width(), oldHeight = image->Height();
    int newWidth = oldWidth * factor, newHeight = oldHeight * factor;
    int newX, newY;
    Pixel newPixel;
    Pixel** newPixels;

    newPixels = image->Resize(newWidth, newHeight, false);
	tmpBuffer->Resize(newWidth, newHeight, true);
    for(int y = 0; y < oldHeight; y++)
    {
        for(int x = 0; x < oldWidth; x++)
        {
            newX = x * factor;
            newY = y * factor;
            newPixel = oldPixels[y][x];

            for(int yRect = newY; yRect < newY + factor; yRect++)
            {
                for(int xRect = newX; xRect < newX + factor; xRect++)
                {
                    newPixels[yRect][xRect] = newPixel;
                }
            }
        }
    }

    MemoryHelper::Deallocate2D(oldPixels, oldHeight);
	CopyTmpBuffer();
    NotifyImageUpdated(true);
}


void Model::ResizeDirectMapping_Interp(int factor)
{
    Pixel** oldPixels = image->Pixels();
    Pixel** newPixels;
    int oldWidth = image->Width(), oldHeight = image->Height();
    int newWidth = oldWidth * factor, newHeight = oldHeight * factor;
    int newX, newY;

    newPixels = image->Resize(newWidth, newHeight, false);
	tmpBuffer->Resize(newWidth, newHeight, true);

    // First pass
    for(int y = 0; y < oldHeight; y++)
    {
        for(int x = 0; x < oldWidth; x++)
        {
            newX = x * factor;
            newY = y * factor;

            newPixels[newY][newX] = oldPixels[y][x];
        }
    }


    Pixel p0, p1;
    float lerpValue;

    // Second pass
    for(int y = 0; y < oldHeight; y++)
    {
        for(int x = 0; x < oldWidth; x++)
        {
            newX = x * factor;
            newY = y * factor;

            p0 = newPixels[newY][newX];
            if(newX + factor >= newWidth)
            {
                for(int xRect = newX + 1; xRect < newX + factor; xRect++)
                {
                    newPixels[newY][xRect] = p0;
                }
            }
            else
            {
                for(int xRect = newX + 1; xRect < newX + factor; xRect++)
                {
                    p1 = newPixels[newY][newX + factor];
                    lerpValue = (xRect - newX) / factor;

                    newPixels[newY][xRect] = MathHelper::Lerp(p0, p1, lerpValue);
                }
            }
        }
    }

    // 3rd pass
    for(int x = 0; x < newWidth; x++)
    {
        for(int y = 0; y < oldHeight; y++)
        {
            newX = x;
            newY = y * factor;

            p0 = newPixels[newY][newX];
            if(newY + factor >= newHeight)
            {
                for(int yRect = newY + 1; yRect < newY + factor; yRect++)
                {
                    newPixels[yRect][newX] = p0;
                }
            }
            else
            {
                for(int yRect = newY + 1; yRect < newY + factor; yRect++)
                {
                    p1 = newPixels[newY + factor][newX];
                    lerpValue = float((yRect - newY)) / float(factor);

                    newPixels[yRect][newX] = MathHelper::Lerp(p0, p1, lerpValue);
                }
            }
        }
    }

    MemoryHelper::Deallocate2D(oldPixels, oldHeight);
	CopyTmpBuffer();
    NotifyImageUpdated(true);
}

void Model::ResizeReverseMapping_Order0(int newWidth, int newHeight)
{
    float WRatio = (float) image->Width() / newWidth;
    float HRatio = (float) image->Height() / newHeight;
    int oldWidth = image->Width(), oldHeight = image->Height();

    Pixel** oldPixels = image->Pixels();
    Pixel** newPixels = image->Resize(newWidth, newHeight, false);
	tmpBuffer->Resize(newWidth, newHeight, true);
    
	for(int y = 0; y < newHeight; y++)
    {
        for(int x = 0; x < newWidth; x++)
        {
            int oldX = (int) (x * WRatio);
            int oldY = (int) (y * HRatio);

            newPixels[y][x] = oldPixels[oldY][oldX];
        }
    }

    MemoryHelper::Deallocate2D(oldPixels, oldHeight);
	CopyTmpBuffer();
    NotifyImageUpdated(true);
}

void Model::ResizeReverseMapping_Bilinear(int newWidth, int newHeight)
{
    float WRatio = (float) image->Width() / newWidth;
    float HRatio = (float) image->Height() / newHeight;
    int x1, x2, y1, y2;
    int oldWidth, oldHeight;
    Pixel p1, p2, p3, p4, z1, z2;
    float xFraction, yFraction;

    oldWidth = image->Width();
    oldHeight = image->Height();

    Pixel** oldPixels = image->Pixels();

    image->Resize(newWidth, newHeight, false);
	tmpBuffer->Resize(newWidth, newHeight, true);

    for(int y = 0; y < newHeight; y++)
    {
        for(int x = 0; x < newWidth; x++)
        {
            float oldX = x * WRatio;
            float oldY = y * HRatio;

            x1 = floor(oldX);
            x2 = x1 + 1;
            y1 = floor(oldY);
            y2 = y1 + 1;

            p1 = oldPixels[y1][x1];

            if(x2 >= oldWidth)
                p2 = p1;
            else
                p2 = oldPixels[y1][x2];

            if(y2 >= oldHeight)
                p3 = p1;
            else
                p3 = oldPixels[y2][x1];

            if(y2 >= oldHeight || x2 >= oldWidth)
                p4 = p1;
            else
                p4 = oldPixels[y2][x2];

            xFraction = oldX - x1;
            yFraction = oldY - y1;

            z1 = MathHelper::Lerp(p1, p2, xFraction);
            z2 = MathHelper::Lerp(p3, p4, xFraction);

            image->Pixels()[y][x] = MathHelper::Lerp(z1, z2, yFraction);
        }
    }

    MemoryHelper::Deallocate2D(oldPixels, oldHeight);
	CopyTmpBuffer();
    NotifyImageUpdated(true);
}


void Model::FlipHorizotally()
{
    for(int y = 0; y < image->Height(); y++)
    {
        for(int x = 0; x < image->Width() / 2; x++)
        {
            swap(image->Pixels()[y][x], image->Pixels()[y][image->Width() - x - 1]);
        }

    }
	CopyTmpBuffer();
    NotifyImageUpdated(false);
}

void Model::FlipVertically()
{
    for(int y = 0; y < image->Height() / 2; y++)
    {
        for(int x = 0; x < image->Width(); x++)
        {
            swap(image->Pixels()[y][x], image->Pixels()[image->Height() - y -1][x]);
        }

    }
	CopyTmpBuffer();
    NotifyImageUpdated(false);
}

void Model::Translate(int dX, int dY)
{
    /*Pixel** oldPixels = pixels;

    AllocatePixels(width, height);

    int newX, newY;
    for(int y = 0; y < height / 2; y++)
    {
        for(int x = 0; x < width; x++)
        {
            newX = x + dX;
            newY = y + dY;

            if(newX >= width || newY >= height)
                continue;

            pixels[newY][newX] = oldPixels[y][x];
        }

    }

    NotifyImageUpdated();*/
}

void Model::Rotate(float theta)
{
    /*for(int y = 0; y < image->Height() / 2; y++)
    {
        for(int x = 0; x < image->Width(); x++)
        {
            swap(image->Pixels()[y][x], image->Pixels()[image->Height() - y -1][x]);
        }
    }

    NotifyImageUpdated();*/
}

/*void Model::DeletePixels(Pixel** garbagePixels, int width, int height)
{
    for(int i = 0; i < height; i++)
        delete pixels[i];

   delete pixels;
}*/

int Model::Segment(SegmentationType sType, double param)
{
	if(image == NULL)
		return 0;
	switch(sType)
	{
	case BasicGlobal:
		return BasicGlobalThresholding(param);
		break;
	case OptimumGlobal:
		return OptimumGlobalThresholding();
		break;
	case Adaptive:
		AdaptiveThresholding(param);
		break;
	}
	return 0;
}

int Model::GetBasicGlobalThreshold(double initialT, int *grayHistogram, double eps)
{
	double Tk, T = initialT;
	if(T == 0) T = 1;
	while(true)
	{
		int objectCnt = 0, backgroundCnt = 0, objectVal = 0, backgroundVal = 0;
		for(int i = 0 ; i < 256 ; i++)
		{
			if(i <= T)
			{
				objectVal += (i * grayHistogram[i]);
				objectCnt += grayHistogram[i];
			}
			else
			{
				backgroundVal += (i * grayHistogram[i]);
				backgroundCnt += grayHistogram[i];
			}
		}
		Tk = ((backgroundVal / backgroundCnt * 1.0) + (objectVal / objectCnt * 1.0)) / 2.0;
		if(fabs(Tk - T) < eps)
			break;
		T = Tk;
	}
	return T;
}

int Model::BasicGlobalThresholding(double eps)
{
	int T = GetBasicGlobalThreshold((double)(histogram->MaxGrayValue + histogram->MinGrayValue) / 2, histogram->GrayHist(), eps);
	Spilit(0, 0, image->Width(), image->Height(), T);
	CopyTmpBuffer();
	NotifyImageUpdated(true);
	return T;
}

int Model::OptimumGlobalThresholding()
{
	double *normalizedHistogram = histogram->NormalizedHistogram();
	double commulativP[256], commulativeM[256], globalMean = 0;

	commulativP[0] = normalizedHistogram[0];
	commulativeM[0] = 0;
	for(int i = 1 ; i < 256 ; i++)
	{
		commulativP[i] = commulativP[i - 1] + normalizedHistogram[i];
		commulativeM[i] = commulativeM[i - 1] + (normalizedHistogram[i] * i);
		globalMean += (normalizedHistogram[i] * i);
	}

	double betweenClassVariance = -1.0;
	int bestT = 1, bestCnt = 0;
	for(int i = histogram->MinGrayValue + 1 ; i < histogram->MaxGrayValue - 1 ; i++)
	{
		double tmpVar = (globalMean * commulativP[i] - commulativeM[i]);
		tmpVar *= tmpVar;
		tmpVar /= (commulativP[i] * (1 - commulativP[i]));
		if(tmpVar > betweenClassVariance)
		{
			betweenClassVariance = tmpVar;
			bestT = i;
			bestCnt = 1;
		}
		else if(tmpVar == betweenClassVariance)
		{
			bestT += i;
			bestCnt++;
		}
	}
	bestT /= bestCnt;
	Spilit(0, 0, image->Width(), image->Height(), bestT);
	CopyTmpBuffer();
	NotifyImageUpdated(true);
	return bestT;
}

void Model::Spilit(int h, int v, int width, int height, int T)
{
	for(int i = 0 ; i < height ; i++)
	{
		for(int j = 0 ; j < width ; j++)
		{
			if(image->Pixels()[v+i][h+j].R >= T)
				image->Pixels()[v+i][h+j] = Pixel(255, 255, 255);
			else
				image->Pixels()[v+i][h+j] = Pixel(0, 0, 0);
		}
	}
}

void Model::AdaptiveThresholding(double winArea)
{
	AdaptiveThresholding(0, 0, image->Width(), image->Height(), winArea);
	CopyTmpBuffer();
	NotifyImageUpdated(true);
}

void Model::AdaptiveThresholding(int h, int v, int width, int height, double winArea)
{
	if(width * height <= winArea)
	{
		int minGrayVal = 255, maxGrayVal = 0;
		int grayHistogram[256];
		double mean = 0, totalSum = 0, totalCnt = 0;
		GetWindowHistogram(h, v, width, height, minGrayVal, maxGrayVal, grayHistogram);
		for(int i = 0 ; i < 256 ; i++)
		{
			totalSum += (i * grayHistogram[i]);
			totalCnt += grayHistogram[i];
		}
		mean = totalSum / totalCnt;
		int T;
		if(minGrayVal == maxGrayVal)
			T = minGrayVal;
		else
			T = GetBasicGlobalThreshold((maxGrayVal + minGrayVal) / 2, grayHistogram, 1);
		Spilit(h, v, width+1, height+1, T);
	}
	else
	{
		AdaptiveThresholding(h, v, width / 2, height / 2, winArea);
		AdaptiveThresholding(h + width / 2, v, width / 2, height / 2, winArea);
		AdaptiveThresholding(h, v + height / 2, width / 2, height / 2, winArea);
		AdaptiveThresholding(h + width / 2, v + height / 2, width / 2, height / 2, winArea);
	}
}

void Model::GetWindowHistogram(int h, int v, int width, int height, int& minGrayVal, int& maxGrayVal, int grayHistogram[])
{
	for(int i = 0 ; i < 256 ; i++)
		grayHistogram[i] = 0;

	for(int i = 0 ; i < height ; i++)
	{
		for(int j = 0 ; j < width ; j++)
		{
			Pixel tmpPixel = image->Pixels()[i+v][j+h];
			int gray = (tmpPixel.R + tmpPixel.G + tmpPixel.B) / 3;
			grayHistogram[gray]++;
			maxGrayVal = max(maxGrayVal, gray);
			minGrayVal = min(minGrayVal, gray);
		}
	}
}

int Model::DoContrast(int oldVal, int newMin, int newMax, int oldMin, int oldMax)
{
	double val = ((double)(oldVal - oldMin) / (double)(oldMax - oldMin) * (double)(newMax - newMin)) + newMin;
	return CutOff((int)val, 0, 255);
}

void Model::Contrast(int value)
{
	if(image == NULL)
		return;

	for(int i = 0 ; i < image->Height() ; i++)
	{
		for(int j = 0 ; j < image->Width() ; j++)
		{
			image->Pixels()[i][j].R = DoContrast(tmpBuffer->Pixels()[i][j].R, histogram->MinRedValue - value, histogram->MaxRedValue + value, histogram->MinRedValue, histogram->MaxRedValue);
			image->Pixels()[i][j].G = DoContrast(tmpBuffer->Pixels()[i][j].G, histogram->MinGreenValue - value, histogram->MaxGreenValue + value, histogram->MinGreenValue, histogram->MaxGreenValue);
			image->Pixels()[i][j].B = DoContrast(tmpBuffer->Pixels()[i][j].B, histogram->MinBlueValue - value, histogram->MaxBlueValue + value, histogram->MinBlueValue, histogram->MaxBlueValue);
		}
	}

	NotifyImageUpdated(false);
}

int Model::CutOff(int val, int mnRange, int mxRange)
{
	if(val < 0)
		return mnRange;
	if(val > mxRange)
		return mxRange;
	return val;
}

void Model::Brightness(int value)
{
	if(image == NULL)
		return;

	for(int i = 0 ; i < image->Height() ; i++)
	{
		for(int j = 0 ; j < image->Width() ; j++)
		{
			image->Pixels()[i][j].R = CutOff(tmpBuffer->Pixels()[i][j].R + value, 0, 255);
			image->Pixels()[i][j].G = CutOff(tmpBuffer->Pixels()[i][j].G + value, 0, 255);
			image->Pixels()[i][j].B = CutOff(tmpBuffer->Pixels()[i][j].B + value, 0, 255);
		}
	}
	NotifyImageUpdated(false);
}

